import {
  tools,
} from '../utils/index';
import {
  ValuePairHash,
} from './valuePair';
import {
  LinkedListSingly,
} from '../LinkedList/index';

const {
  // djb2HashCode as hashConversion,
  hashConversion,
  defaultEaquals,
} = tools;

/**
 * @description HashTable --> Detach link
 */
export class HashTableDL {
  protected table: {
    [key: string]: LinkedListSingly | undefined | null;
  }

  public constructor(tableInit: Dictionary.TTableInitial = []) {
    this.table = {};

    for (let val of tableInit) {
      this.put(val[0], val[1]);
    }
  }

  public put(key: Dictionary.TTableKey, value: any) {
    const position = this.hashCode(key);
    const posValue = this.table[position];
    const newValue = new ValuePairHash(key, value);
    if (posValue) {
      const keyIndex = posValue.indexOf(key);
      if (keyIndex > -1) {
        const valuePair = posValue.getNodeAt(keyIndex);
        valuePair && (valuePair.value = newValue); 
      } else {
        posValue.push(newValue);
      }
    } else {
      this.table[position] = new LinkedListSingly([newValue], this.equalValueByKey);
    }
  }

  public get(key: Dictionary.TTableKey) {
    const valueLinkedList = this.table[this.hashCode(key)];
    let value: any;

    if (valueLinkedList) {
      const index = valueLinkedList.indexOf(key);
      if (index > -1) {
        value = valueLinkedList.getElementAt(index).value;
      }
    }

    return value;
  }

  public remove(key: Dictionary.TTableKey): boolean {
    const code = this.hashCode(key);
    const valuePair = this.table[code] ?? undefined;

    if (valuePair === undefined) {
      return false;
    } else {
      if (!valuePair.isEmpty()) {
        this.table[code] = undefined;
      } else {
        const index = valuePair.indexOf(key);

        if (index > -1) {
          valuePair.removeAt(index);
          return true;
        }
      }
      return false;
    }
  }

  public entries() {
    const tableEntries = Object.values(this.table);
    const entries: any[] = [];

    tableEntries.forEach(link => {
      if (link !== undefined && link !== null) {
        let current = link.getNodeAt(0);

        while (current) {
          const item = current.value as ValuePairHash;
          entries.push([item.key, item.value]);
          current = current.next;
        }
      }
    });

    return entries;
  }

  protected hashCode(key: any) {
    return hashConversion(key);
  }
  protected equalValueByKey(linkNodeValue: ValuePairHash, value: any) {
    return defaultEaquals(linkNodeValue.key, value);
  }
}

/**
 * @description HashTable --> Linear probe
 */
export class HashTableLP {
  protected table: {
    [key: string]: ValuePairHash | undefined | null;
  }

  public constructor(tableInit: Dictionary.TTableInitial = []) {
    this.table = {};

    for (let val of tableInit) {
      this.put(val[0], val[1]);
    }
  }

  public put(key: Dictionary.TTableKey, value: any) {
    let position = this.hashCode(key);
    let posValue = this.table[position];
    const newValue = new ValuePairHash(key, value);
    if (posValue) {
      while(posValue && posValue.key !== key) {
        posValue = this.table[++position];
      }
      this.table[position] = newValue;
    } else {
      this.table[position] = newValue;
    }
  }

  public get(key: Dictionary.TTableKey) {
    let position = this.hashCode(key);
    let keyValue = this.table[position];

    if (!keyValue) {
      return undefined;
    } else {
      if (keyValue.key === key) {
        return keyValue.value;
      } else {
        while(keyValue && keyValue.key !== key) {
          keyValue = this.table[++position];
        }
        return keyValue?.value;
      }
    }
  }

  public remove(key: Dictionary.TTableKey): boolean {
    let position = this.hashCode(key);
    let keyValue = this.table[position];
    let flagReturn: boolean;

    if (!keyValue) {
      flagReturn = false;
    } else {
      if (keyValue.key === key) {
        delete this.table[position];
        flagReturn = true;
      } else {
        while(keyValue && keyValue.key !== key) {
          keyValue = this.table[++position];
        }
        if (keyValue) {
          delete this.table[position];
          flagReturn = true;
        } else {
          flagReturn = false;
        }
      }
    }

    if (flagReturn) {
      this.verifyRemoveEffect(key, position);
    }

    return flagReturn;
  }

  public entries() {
    const tableEntries = Object.values(this.table);
    const entries: any[] = [];

    tableEntries.forEach(valuePair => {
      if (valuePair !== undefined && valuePair !== null) {
        entries.push([valuePair.key, valuePair.value]);
      }
    });

    return entries;
  }

  protected hashCode(key: any) {
    return hashConversion(key);
  }

  protected verifyRemoveEffect(key: any, removedPositionInput) {
    const hash = this.hashCode(key);
    let removedPosition = removedPositionInput;
    let index = removedPosition + 1;
    let current = this.table[index];

    while(current) {
      const posHash = this.hashCode(current.key);
      
      if (posHash <= hash || posHash <= removedPosition) {
        this.table[removedPosition] = current;
        delete this.table[index];
        removedPosition = index;
      }
      index++;
      current = this.table[index];
    }

  }
}
